﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Windows.Forms;
using System.IO;
using System.Collections.Specialized;
using System.Net;

using Factonomy.AzureStorageUtility.WindowsApp.Azure.Storage;
using Factonomy.AzureStorageUtility.WindowsApp.Azure.Storage.StorageHttpConstants;

namespace Factonomy.AzureStorageUtility.WindowsApp.Azure
{
    class BlobManager : IDisposable
    {
        private StorageAccountInfo _account = null;
        private BlobStorage _blobStorage = null;

        public delegate void ListViewDelegate(ListViewItem item);
        public delegate void RemoveItemDelegate();
        public delegate void AddBlobDelegate();
        public delegate void NoParameterDelegate();
        public delegate void ErrorDelegate(Exception ex);

        public event ListViewDelegate ShowContainer;
        public event ListViewDelegate ShowBlob;
        public event RemoveItemDelegate RemoveBlobs;
        public event RemoveItemDelegate RemoveContainer;
        public event AddBlobDelegate AddBlob;
        public event ErrorDelegate Error;
        public event ErrorDelegate LoadError;
        public event NoParameterDelegate Loaded;
        public event NoParameterDelegate Complete;

        protected virtual void OnShowContainer(ListViewItem e)
        {
            if (ShowContainer != null)
                ShowContainer(e);
        }

        protected virtual void OnShowBlob(ListViewItem e)
        {
            if (ShowBlob != null)
                ShowBlob(e);
        }

        protected virtual void OnRemoveBlobs()
        {
            if (RemoveBlobs != null)
                RemoveBlobs();
        }

        protected virtual void OnRemoveContainer()
        {
            if (RemoveContainer != null)
                RemoveContainer();
        }

        protected virtual void OnAddBlob()
        {
            if (AddBlob != null)
                AddBlob();
        }

        protected virtual void OnError(Exception ex)
        {
            if (Error != null)
                Error(ex);
        }

        protected virtual void OnLoaded()
        {
            if (Loaded != null)
                Loaded();
        }

        protected virtual void OnLoadError(Exception ex)
        {
            if (LoadError != null)
                LoadError(ex);
        }

        protected virtual void OnComplete()
        {
            if (Complete != null)
                Complete();
        }

        public BlobStorage BlobStorage
        {
            get { return this._blobStorage; }
        }

        public StorageAccountInfo Account
        {
            get { return this._account; }
        }

        public static bool AccountExists(string account)
        {
            bool value = false;
            value = ConfigurationManager.AppSettings[account + ".AccountSharedKey"] == null ? value : true;
            value = ConfigurationManager.AppSettings[account + ".BlobStorageEndpoint"] == null ? value : true;
            value = ConfigurationManager.AppSettings[account + ".QueueStorageEndpoint"] == null ? value : true;
            value = ConfigurationManager.AppSettings[account + ".TableStorageEndpoint"] == null ? value : true;
            value = ConfigurationManager.AppSettings[account + ".UsePathStyleUris"] == null ? value : true;
            return value;
        }

        public BlobManager()
        {
            
        }

        public void Load()
        {
            try
            {
                if (ConfigurationManager.AppSettings["accounts"] == null)
                {
                    this.OnError(new Exception("The configuration app setting accounts is 'null' where a value is required.  Please check the configuration file."));
                }

                if (ConfigurationManager.AppSettings["defaultaccount"] != null)
                {
                    if (AccountExists(ConfigurationManager.AppSettings["defaultaccount"]))
                    {
                        this.Load(ConfigurationManager.AppSettings["defaultaccount"]);
                    }
                }

                if (this._account == null)
                {
                    string[] accounts = ConfigurationManager.AppSettings["accounts"].Split('|');
                    foreach (string account in accounts)
                    {
                        if (AccountExists(account))
                        {
                            this.Load(account);
                        }
                    }
                }

                if (this._account == null)
                {
                    this.OnError(new Exception("No valid account has been found.  Please check the configuration file."));
                }

                this.Test();
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }
        }

        private void Test()
        {
            try
            {
                ResourceUriComponents uriComponents = new ResourceUriComponents(this._account.AccountName, "", null);
                Uri containerUri = HttpRequestAccessor.ConstructResourceUri(this._account.BaseUri, uriComponents, this._account.UsePathStyleUris);
                WebRequest request = WebRequest.Create(containerUri);
                WebResponse response = request.GetResponse();
            }
            catch (WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ConnectFailure)
                {
                    this.OnLoadError(new Exception(ex.Message));
                }
                else
                {
                    this.OnLoaded();
                }
            }
        }

        public bool Test(string accoutName, Uri baseUri, bool usePathUris, string sharedKey)
        {
            bool result = false;
            {
                try
                {
                    ResourceUriComponents uriComponents;
                    NameValueCollection param = new NameValueCollection();
                    param.Add("comp", "list");

                    Uri uri = Utilities.CreateRequestUri(baseUri, usePathUris, accoutName, null, null, new TimeSpan(0, 0, 10), param, out uriComponents);
                    HttpWebRequest request = Utilities.CreateHttpRequest(uri, HttpMethod.Get, new TimeSpan(0, 0, 10));
                    SharedKeyCredentials credentials = new SharedKeyCredentials(accoutName, Convert.FromBase64String(sharedKey));
                    credentials.SignRequest(request, uriComponents);

                    try
                    {
                        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                        {
                            if (response.StatusCode == HttpStatusCode.OK)
                                result = true;
                            else if (response.StatusCode == HttpStatusCode.Gone || response.StatusCode == HttpStatusCode.NotFound)
                                result = false;
                            else
                            {
                                Utilities.ProcessUnexpectedStatusCode(response);
                                result = false;
                            }

                            response.Close();
                        }
                    }
                    catch (WebException we)
                    {
                        if (we.Response != null &&
                            ((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Gone ||
                            (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.NotFound)
                            )
                            result = false;
                        else
                            throw Utilities.TranslateWebException(we);
                    }
                }
                catch (Exception ex)
                {
                    result = false;
                }
            }
            return result;
        }

        public void Load(string accountName)
        {
            try
            {
                // TODO Test with no settings or wrong configuration
                string sharedKey = ConfigurationManager.AppSettings[accountName + ".AccountSharedKey"];
                string blobStorageEndpoint = ConfigurationManager.AppSettings[accountName + ".BlobStorageEndpoint"];
                string queueStorageEndpoint = ConfigurationManager.AppSettings[accountName + ".QueueStorageEndpoint"];
                string tableStorageEndpoint = ConfigurationManager.AppSettings[accountName + ".TableStorageEndpoint"];
                string usePathStyleUrisString = ConfigurationManager.AppSettings[accountName + ".UsePathStyleUris"];
                bool usePathStyleUris = false;

                if (usePathStyleUrisString != null)
                {
                    usePathStyleUris = bool.Parse(usePathStyleUrisString);
                }

                this._account = new StorageAccountInfo(new Uri(blobStorageEndpoint), usePathStyleUris, accountName, sharedKey);

                this._blobStorage = BlobStorage.Create(this._account);
                this._blobStorage.Timeout = new TimeSpan(0, 0, 10);
                this._blobStorage.RetryPolicy = RetryPolicies.RetryN(2, TimeSpan.FromMilliseconds(100));

                this.Test();

                
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }
        }

        public void SaveBlob(string containerName, string blobName, string contentText)
        {
            try
            {
                BlobContainer container = this._blobStorage.GetBlobContainer(containerName);

                MemoryStream stream = new MemoryStream();
                byte[] data = Encoding.UTF8.GetBytes(contentText);
                stream.Write(data, 0, data.Length);

                stream.Seek(0, SeekOrigin.Begin);

                BlobContents contents = new BlobContents(stream);
                BlobProperties props = new BlobProperties(blobName);
                props.ContentType = Globals.GetHttpContentType(Path.GetExtension(blobName));
                container.CreateBlob(props, contents, true);
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }
        }

        public void ListContainers()
        {
            try
            {
                foreach (BlobContainer container in this._blobStorage.ListBlobContainers())
                {
                    this.OnShowContainer(new ListViewItem(container.ContainerName, "folder"));
                }
                this.OnComplete();
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }
        }

        public void ListBlobs(string containerName)
        {
            try
            {
                if (containerName != null && containerName != string.Empty)
                {
                    try
                    {
                        this.OnRemoveBlobs();

                        BlobContainer container = this._blobStorage.GetBlobContainer(containerName);
                        IEnumerable<object> blobs = container.ListBlobs("", false);

                        if (blobs != null)
                        {
                            try
                            {
                                foreach (object prop in blobs)
                                {
                                    ListViewItem item = new ListViewItem();

                                    string name = string.Empty;
                                    if (prop.GetType() == typeof(BlobProperties))
                                    {
                                        BlobProperties prop1 = (BlobProperties)prop;
                                        item.Text = prop1.Name;

                                        ListViewItem.ListViewSubItem encodingItem = new ListViewItem.ListViewSubItem();
                                        encodingItem.Name = "Type";
                                        encodingItem.Text = prop1.ContentType;
                                        item.SubItems.Add(encodingItem);

                                        ListViewItem.ListViewSubItem sizeItem = new ListViewItem.ListViewSubItem();
                                        sizeItem.Name = "Size";
                                        sizeItem.Text = prop1.ContentLength.ToString();
                                        item.SubItems.Add(sizeItem);

                                    }

                                    if (prop.GetType() == typeof(string))
                                    {
                                        item.Text = prop.ToString();
                                    }


                                    this.OnShowBlob(item);

                                }

                                this.OnComplete();
                            }
                            catch (Exception ex)
                            {
                                this.OnError(new Exception(ex.Message));
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        this.OnError(new Exception(ex.Message));
                    }
                }

            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured while trying to list the containers blobs.", ex));
            }
        }

        public BlobProperties GetBlobProperties(string containerName, string blobName)
        {
            try
            {
                BlobContainer container = this._blobStorage.GetBlobContainer(containerName);
                MemoryStream stream = new MemoryStream();
                BlobContents contents = new BlobContents(stream);
                BlobProperties properties = container.GetBlob(blobName, contents, false);
                
                this.OnComplete();

                return properties;
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }

            return null;
        }

        public MemoryStream GetBlobContents(string containerName, string blobName)
        {
            try
            {
                BlobContainer container = this._blobStorage.GetBlobContainer(containerName);
                MemoryStream stream = new MemoryStream();
                BlobContents contents = new BlobContents(stream);
                BlobProperties properties = container.GetBlob(blobName, contents, false);
                stream.Seek(0, SeekOrigin.Begin);
                
                this.OnComplete(); 
                
                return stream;
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }
            
            return null;
        }

        public void Dispose()
        {
            this._account = null;
            this._blobStorage = null;
            this.RemoveBlobs = null;
            this.ShowBlob = null;
        }

        public void AddFile(string containerName, string path, string blobName)
        {
            BlobContainer container = this._blobStorage.GetBlobContainer(containerName);
            
            try
            {
                using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read))
                {
                    BlobContents contents = new BlobContents(stream);
                    BlobProperties props = new BlobProperties(blobName);
                    props.ContentType = Globals.GetHttpContentType(Path.GetExtension(path));
                    container.CreateBlob(props, contents, true);
                    this.OnAddBlob();
                }
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }
        }

        public void AddDirectory(string containerName, string path, string root)
        {
            try
            {
                if (!path.Contains(".svn"))
                {
                    BlobContainer container = this._blobStorage.GetBlobContainer(containerName);

                    DirectoryInfo directory = new DirectoryInfo(path);
                    if (directory.GetFiles().Length > 0)
                    {
                        BlobContents contents = new BlobContents(new MemoryStream());
                        BlobProperties props = new BlobProperties(path.Replace(root, ""));
                        container.CreateBlob(props, contents, true);
                    }

                    foreach (FileInfo file in directory.GetFiles())
                    {
                        using (FileStream stream = File.Open(file.FullName, FileMode.Open, FileAccess.Read))
                        {
                            BlobContents contents = new BlobContents(stream);
                            BlobProperties props = new BlobProperties(file.FullName.Replace(root, ""));
                            props.ContentType = Globals.GetHttpContentType(Path.GetExtension(file.FullName));
                            container.CreateBlob(props, contents, true);
                        }
                    }

                    foreach (DirectoryInfo subDir in directory.GetDirectories())
                    {
                        this.AddDirectory(containerName, subDir.FullName, root);
                    }

                    this.OnAddBlob();
                    this.OnComplete();
                }
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }
        }

        public void AddContainer(string containerName)
        {
            BlobContainer container = this._blobStorage.GetBlobContainer(containerName);
            NameValueCollection containerMetadata = new NameValueCollection();
            containerMetadata.Add("Name", containerName);
            container.CreateContainer(containerMetadata, ContainerAccessControl.Public);
        }

        public bool DeleteContainer(string containerName)
        {
            BlobContainer container = this._blobStorage.GetBlobContainer(containerName);

            foreach (object prop in container.ListBlobs("", false))
            {
                try
                {
                    if (prop.GetType() == typeof(BlobProperties))
                    {
                        BlobProperties prop1 = (BlobProperties)prop;
                        container.DeleteBlob(prop1.Name);
                    }

                    if (prop.GetType() == typeof(string))
                    {
                        container.DeleteBlob(prop.ToString());
                    }

                }
                catch (Exception ex)
                {
                    this.OnError(new Exception(ex.Message));
                }
            }

            if (!container.DeleteContainer())
            {
                return false;
            }
            else
            {
                this.OnRemoveContainer();
                return true;
            }
        }

        public void DeleteBlob(string containerName, string blobName)
        {
            try
            {
                BlobContainer container = this._blobStorage.GetBlobContainer(containerName);
                container.DeleteBlob(blobName);
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }
        }

        public void DeleteAllBlobs(string containerName)
        {
            try
            {
                BlobContainer container = this._blobStorage.GetBlobContainer(containerName);
                foreach (object prop in container.ListBlobs("", false))
                {

                    if (prop.GetType() == typeof(BlobProperties))
                    {
                        BlobProperties prop1 = (BlobProperties)prop;
                        container.DeleteBlob(prop1.Name);
                    }

                    if (prop.GetType() == typeof(string))
                    {
                        container.DeleteBlob(prop.ToString());
                    }
                }

                this.OnComplete();
            }
            catch (Exception ex)
            {
                this.OnError(new Exception(ex.Message));
            }
        }

        

    }
}
